﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace DNX.IoCnoC
{
    public interface IDependenciesMapper
    {
        void LifeStyleTransientFor(Type type);
        void SetValueFor(Type type, string parameter, object value);
        ITypeHandle Resolve(Type type);
    }

    public class DependenciesMapper : IDependenciesMapper
    {
        private readonly IDefaultConstructor defaultConstructor;
        private readonly IDerivetatedTypes derivetatedTypes;
        private readonly IDictionary<Type, ITypeHandle> map;
        private readonly List<Type> transients;
        private readonly IDictionary<Type, IParametersValueHandle> parameterValues;
        private readonly IDictionary<Type, ManualRegistration> manualRegistrations;

        public DependenciesMapper()
            : this(new CompositeDefaultConstructor(),new DerivetatedTypes())
        {
        }

        public DependenciesMapper(IDefaultConstructor defaultConstructor, IDerivetatedTypes derivetatedTypes)
        {
            this.defaultConstructor = defaultConstructor;
            this.derivetatedTypes = derivetatedTypes;
            map = new Dictionary<Type, ITypeHandle>();
            transients = new List<Type>();
            parameterValues = new Dictionary<Type, IParametersValueHandle>();
            manualRegistrations = new Dictionary<Type, ManualRegistration>();
        }

        private void Map(Type type)
        {
            if (IsExcluded(type))
                return;

            if (map.ContainsKey(type))
                return;

            var typeHandle = new TypeHandle
            {
                LifeStyle = LifeStyle(type),
                Type = type,
            };

            map.Add(type, typeHandle);
            typeHandle.Twins = derivetatedTypes.GetTypes(type).Select(Resolve).Where(i=>i!=null && !i.Type.IsAbstract).ToArray();

            var constructorInfos = type.GetConstructors();

            var parameterValue = ParameterValueConfiguration(type);

            foreach (var constructorInfo in constructorInfos)
            {
                var constructor = new Constructor();
                typeHandle.Constructors.Add(constructor);

                foreach (var parameterInfo in constructorInfo.GetParameters())
                {
                    var parameterType = parameterInfo.ParameterType;
                    var parameter = CreateResolution(parameterType);
                    constructor.Parameters.Add(parameter);
                    if (!parameterValue.Parameters.ContainsKey(parameterInfo.Name))
                        parameter.Implementations.AddRange(MapImplementations(parameterType));
                    else
                        parameter.DefaultValue = parameterValue.Parameters[parameterInfo.Name];
                }
            }
            defaultConstructor.Set(constructorInfos, typeHandle);

            foreach (var propertyInfo in type.GetProperties().Where(p => p.GetSetMethod() != null))
            {
                if (propertyInfo.GetCustomAttribute<ResolvePropertyAttribute>() == null)
                    continue;

                var parameterType = propertyInfo.PropertyType;
                if (parameterValue.Parameters.ContainsKey(propertyInfo.Name)) 
                    continue;
                MapImplementations(parameterType);
                typeHandle.Properties.Add(parameterType);
            }

            foreach (var attribute in type
                .GetCustomAttributes(typeof (BehaviorAttribute), true)
                .Where(a => !a.GetType().IsAbstract)
                .Where(attribute => !map.ContainsKey(attribute.GetType())))

                Resolve(attribute.GetType()).LifeStyle = new TransientAction();
        }

        private static bool IsExcluded(MemberInfo type)
        {
            return type.GetCustomAttributes(typeof(ExcludedObjectAttribute)).Any();
        }

        private Type[] MapImplementations(Type parameterType)
        {
            var implementations = Implementations(parameterType);

            if (implementations.Any()) 
                return implementations;
            return manualRegistrations.ContainsKey(parameterType) 
                ? manualRegistrations[parameterType].Implementations().ToArray() 
                : new Type[0];
        }

        private Type[] Implementations(Type parameterType)
        {
            return derivetatedTypes.GetTypes(parameterType.IsArray 
                ? parameterType.GetElementType() 
                : parameterType);
        }

        private IParametersValueHandle ParameterValueConfiguration(Type type)
        {
            return parameterValues.ContainsKey(type)
                ? parameterValues[type]
                : new ParametersHandle();
        }

        private static IParameterHandle CreateResolution(Type type)
        {
            var resoultion = new ParameterHandle
            {
                Contract = type,
            };
            return resoultion;
        }

        private ILifeStyleAction LifeStyle(Type type)
        {
            if (parameterValues.ContainsKey(type))
                return new TransientAction();

            if (transients.Contains(type))
                return new TransientAction();

            if (type.IsGenericType && transients.Any(i=> string.Format("{0}.{1}", i.Namespace, i.Name) == string.Format("{0}.{1}", type.Namespace, type.Name)))
                return new TransientAction();

            return new SingletonAction();
        }

        public void LifeStyleTransientFor(Type type)
        {
            if (transients.Contains(type))
                return;

            transients.Add(type);
        }

        public void SetValueFor(Type type, string parameter, object value)
        {
            if (!parameterValues.ContainsKey(type))
            {
                var parameterValueConfiguration = new ParametersHandle
                {
                    Type = type,
                };
                parameterValues.Add(type, parameterValueConfiguration);
            }
            parameterValues[type].Parameters.Add(parameter, value);
        }

        public ITypeHandle Resolve(Type type)
        {
            Map(type);
            if (map.ContainsKey(type))
                return map[type];
            return null;
        }

        public void MapFor(Type contract, Type type)
        {
            if (!manualRegistrations.ContainsKey(contract))
            {
                var manualRegistration = new ManualRegistration();
                manualRegistrations.Add(contract, manualRegistration);
            }
            manualRegistrations[contract].Add(type);
        }
    }

    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    public sealed class ExcludedObjectAttribute : Attribute
    {
    }
}